package com.ikingtech.platform.service.oss.controller;

import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.ikingtech.framework.sdk.base.model.BatchParam;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.oss.api.OssApi;
import com.ikingtech.framework.sdk.oss.embedded.core.FileTemplate;
import com.ikingtech.framework.sdk.oss.embedded.core.OssResponse;
import com.ikingtech.framework.sdk.oss.embedded.properties.OssProperties;
import com.ikingtech.framework.sdk.oss.model.OssFileDTO;
import com.ikingtech.framework.sdk.oss.model.OssFileQueryParamDTO;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.oss.entity.OssFileDO;
import com.ikingtech.platform.service.oss.exception.OssExceptionInfo;
import com.ikingtech.platform.service.oss.service.OssService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;

import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/oss", name = "文件中心", description = "文件中心")
public class OssController implements OssApi {

    private final OssService service;

    private final FileTemplate template;

    private final OssProperties properties;

    @Override
    public R<OssFileDTO> update(OssFileDTO file) {
        if (!this.service.exist(file.getId())) {
            throw new FrameworkException(OssExceptionInfo.FILE_NOT_FOUND);
        }
        this.service.updateById(Tools.Bean.copy(file, OssFileDO.class));
        return R.ok();
    }

    @Override
    public R<List<OssFileDTO>> page(OssFileQueryParamDTO queryParam) {
        return R.ok(this.service.listPage(queryParam).convert(entity -> Tools.Bean.copy(entity, OssFileDTO.class)));
    }

    @Override
    public R<List<OssFileDTO>> all() {
        return R.ok(Tools.Coll.convertList(this.service.list(), entity -> Tools.Bean.copy(entity, OssFileDTO.class)));
    }

    @Override
    public R<List<OssFileDTO>> listByIds(BatchParam<String> ids) {
        if (Tools.Coll.isBlank(ids.getList())) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(Tools.Coll.convertList(this.service.listByIds(ids.getList()), entity -> Tools.Bean.copy(entity, OssFileDTO.class)));
    }

    /**
     * 上传文件
     *
     * @param file 文件对象
     * @param dir 文件存储路径
     * @return 文件信息
     */
    @Override
    public R<OssFileDTO> upload(MultipartFile file, String dir) {
        preCheck(file);
        String originalFilename = this.preCheck(file);
        InputStream inputStream = null;
        OssFileDTO result;
        try {
            inputStream = file.getInputStream();
            result = this.upload(dir, originalFilename, inputStream, file.getContentType());
        } catch (Exception e) {
            throw new FrameworkException(OssExceptionInfo.GET_FILE_INPUT_STREAM_FAIL);
        } finally {
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        return R.ok(result);
    }

    /**
     * 上传文件(Byte数组)
     *
     * @param fileByte 文件Byte数组
     * @return 文件信息
     */
    @Override
    public R<OssFileDTO> uploadByte(String fileName, String dir, byte[] fileByte) {
        if (Tools.Str.isBlank(fileName)) {
            throw new FrameworkException(OssExceptionInfo.FILE_ORIGIN_NAME_CAN_NOT_BE_EMPTY);
        }

        try {
            return R.ok(this.upload(dir, fileName, ByteSource.wrap(fileByte).openStream(), MediaType.APPLICATION_OCTET_STREAM_VALUE));
        } catch (NullPointerException | IOException e) {
            throw new FrameworkException(OssExceptionInfo.UPLOAD_FILE_ERROR);
        }
    }

    /**
     * 下载文件
     *
     * @param fileUrl  文件相对路径
     * @param response 请求响应
     */
    @Override
    public void download(String fileUrl, Boolean preview, HttpServletResponse response) {
        OssFileDO entity = this.service.getByFileUrl(fileUrl);
        if (null == entity) {
            throw new FrameworkException(OssExceptionInfo.FILE_NOT_FOUND);
        }
        try {
            OssResponse result = this.template.getObject(this.properties.getDefaultBucketName(), entity.getPath());
            response.setContentType(result.getContentType());
            if (!Boolean.TRUE.equals(preview)) {
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(entity.getOriginName(), Charset.defaultCharset()));
            }
            ByteStreams.copy(result.getObjectStream(), response.getOutputStream());
        } catch (Exception e) {
            throw new FrameworkException(OssExceptionInfo.FILE_NOT_FOUND);
        }
    }

    /**
     * 下载文件(Byte数组)
     *
     * @param fileUrl 文件相对路径
     */
    @Override
    public R<byte[]> downloadByte(String fileUrl) {
        OssFileDO entity = this.service.getByFileUrl(fileUrl);
        if (null == entity) {
            throw new FrameworkException(OssExceptionInfo.FILE_NOT_FOUND);
        }
        try {
            OssResponse result = this.template.getObject(entity.getDirName(), entity.getPath());
            return R.ok(ByteStreams.toByteArray(result.getObjectStream()));
        } catch (Exception e) {
            throw new FrameworkException(OssExceptionInfo.FILE_NOT_FOUND);
        }
    }

    /**
     * 删除文件
     *
     * @param fileUrl 文件相对路径
     * @return 执行结果
     */
    @Override
    public R<Object> delete(String fileUrl) {
        OssFileDO entity = this.service.getByFileUrl(fileUrl);
        if (null == entity) {
            throw new FrameworkException(OssExceptionInfo.FILE_NOT_FOUND);
        }
        this.template.removeObject(entity.getDirName(), entity.getPath());
        this.service.removeByFileUrl(fileUrl);
        return R.ok();
    }

    private OssFileDTO upload(String dir, String originalFileName, InputStream inputStream, String contentType) {
        String fileId = Tools.Id.uuid();
        String filePath;
        String fileSuffix = originalFileName.substring(originalFileName.lastIndexOf('.') + 1);
        long fileSize;

        try {
            fileSize = inputStream.available();
            filePath = this.template.putObject(this.properties.getDefaultBucketName(), fileId + "." + fileSuffix, inputStream, contentType);
        } catch (IOException e) {
            throw new FrameworkException(OssExceptionInfo.UPLOAD_FILE_ERROR);
        }

        return this.saveOssFile(originalFileName, fileId, filePath, fileSuffix, fileSize, dir);
    }

    private String preCheck(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new FrameworkException(OssExceptionInfo.FILE_CAN_NOT_BE_EMPTY);
        }

        String originalFilename = file.getOriginalFilename();
        if (null == originalFilename || Tools.Str.isBlank(originalFilename)) {
            throw new FrameworkException(OssExceptionInfo.FILE_ORIGIN_NAME_CAN_NOT_BE_EMPTY);
        }
        return originalFilename;
    }

    private OssFileDTO saveOssFile(String originalFileName, String fileId, String filePath, String fileSuffix, long fileSize, String dir) {
        OssFileDO entity = new OssFileDO();
        entity.setId(fileId);
        entity.setDomainCode(Me.domainCode());
        entity.setTenantCode(Me.tenantCode());
        entity.setAppCode(Me.appCode());
        entity.setDirName(dir);
        entity.setOriginName(originalFileName);
        entity.setSuffix(fileSuffix);
        entity.setFileSize(fileSize);
        entity.setUrl(fileId);
        entity.setPath(filePath);
        this.service.save(entity);
        return Tools.Bean.copy(entity, OssFileDTO.class);
    }
}
